<?php

namespace Bete\Foundation;

use Bete\Foundation\Container;

class Application extends Container
{

    public static $app;

    protected $basePath;

    protected $isBootstrapped = false;

    protected $deferredServices = [];

    protected $serviceProviders = [];

    protected $loadedProviders = [];

    protected $booted = false;

    public function __construct($basePath = null)
    {
        $this->setBasePath($basePath);

        $this->registerBaseBindings();

        $this->bindPaths();
    }

    public function setBasePath($basePath)
    {
        $this->basePath = rtrim($basePath, '\/');
    }

    public function registerBaseBindings()
    {
        static::setInstance($this);

        $this->instance('Bete\Foundation\Application', $this);
    }

    public static function setInstance(Application $app)
    {
        static::$app = $app;
    }

    public static function getInstance()
    {
        return static::$app;
    }

    public function runningInConsole()
    {
        return php_sapi_name() == 'cli';
    }

    protected function bindPaths()
    {
        $this->instance('path', $this->basePath());
        $this->instance('path.view', $this->viewPath());
        $this->instance('path.config', $this->configPath());
        $this->instance('path.storage', $this->storagePath());
        $this->instance('path.compiled', $this->compiledPath());
        $this->instance('path.log', $this->logPath());
    }

    public function basePath()
    {
        return $this->basePath;
    }

    public function viewPath()
    {
        return $this->basePath() . DIRECTORY_SEPARATOR . 'views';
    }

    public function configPath()
    {
        return $this->basePath() . DIRECTORY_SEPARATOR . 'config';
    }

    public function storagePath()
    {
        return $this->basePath() . DIRECTORY_SEPARATOR . 'storage';
    }

    public function compiledPath()
    {
        return $this->storagePath() . DIRECTORY_SEPARATOR . 'compiled';
    }

    public function logPath()
    {
        return $this->storagePath() . DIRECTORY_SEPARATOR . 'logs';
    }

    public function bootstrapWith(array $bootstrappers)
    {
        $this->isBootstrapped = true;

        foreach ($bootstrappers as $bootstrapper) {
            $this->make($bootstrapper)->bootstrap();
        }
    }

    public function isBootstrapped()
    {
        return $this->isBootstrapped;
    }

    public function boot()
    {
        if ($this->booted) {
            return;
        }

        array_walk($this->serviceProviders, function($p) {
            $this->bootProvider($p);
        });

        $this->booted = true;
    }

    public function make($abstract, array $parameters = [])
    {
        $abstract = $this->getAlias($abstract);

        if (isset($this->deferredServices[$abstract])) {
            $this->loadDeferredService($abstract);
        }

        return parent::make($abstract, $parameters);
    }

    public function register($provider, $options = [])
    {
        if ($registered = $this->getProvider($provider)) {
            return $registered;
        }

        if (is_string($provider)) {
            $provider = $this->resolveProviderClass();
        }

        if (method_exists($provider, 'register')) {
            $provider->register();
        }

        foreach ($options as $key => $value) {
            $this[$key] = $value;
        }

        $this->markAsRegistered($provider);

        if ($this->booted) {
            $this->bootProvider($provider);
        }

        return $provider;
    }

    public function bootProvider($provider)
    {
        if (method_exists($provider, 'boot')) {
            return $provider->boot();
        }
    }

    public function getProvider($provider)
    {
        $name = is_string($provider) ? $provider : get_class($provider);

        foreach ($this->serviceProviders as $provider) {
            if ($provider instanceof $name) {
                return $provider;
            }
        }

        return false;
    }

    protected function markAsRegistered($provider)
    {
        $this->serviceProviders[] = $provider;
    }

    public function resolveProviderClass($provider)
    {
        return new $provider($this);
    }

    public function loadDeferredService($service)
    {
        if (!isset($this->deferredServices[$service])) {
            return;
        }

        $provider = $this->deferredServices[$service];

        if (!isset($this->loadedProviders[$provider])) {
            $this->registerDeferredProvider($provider, $service);
        }
    }

    public function registerDeferredProvider($provider, $service = null)
    {
        if ($service) {
            unset($this->deferredServices[$service]);
        }

        $this->register($instance = new $provider($this));
    }

    public function addDeferredServices(array $services)
    {
        $this->deferredServices = 
            array_merge($this->deferredServices, $services);
    }

    public function env()
    {
        if (func_num_args() > 0) {
            $envs = func_get_args();
            foreach ($envs as $env) {
                if ($env == $this->config['app.env']) {
                    return true;
                }
            }
            return false;
        }

        return $this->config['app.env'];
    }

}

